/*
 * PostgresqlConnection object defines the needed connection functions for the dbConnect PostgrSQL driver
 * Copyright (C) 2003 Johnathan Ingram, jingram@rogueware.org
 *
 * This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
 *
 */


#include "pgsqlConnection.h"


#include "pgsqlQuery.h"


// -----------------------------------------------------------------------------
// PRIVATE:
// -----------------------------------------------------------------------------


// -----------------------------------------------------------------------------
// PostgresqlConnection::_pgsqlConnect
// -----------------------------------------------------------------------------
void 
PostgresqlConnection::_pgsqlConnect(
         int index)
{  
   // Make sure the handle is valid and we are not already connected.
   if (index > _numHandles || index < 0)
      throw Error("_pgsqlConnect(): Invalid index to database handle.");

   if (_handles[index]->status != BaseHandle::DISCONNECTED)
      throw AlreadyConnected("_pgsqlConnect(): "
         "The database connection is already connected to the database.");

   // Connect to the PostgreSQL database.   
   _handles[index]->__conn = PQsetdbLogin(host.c_str(), _pgsqlOptions.port.c_str(), NULL, NULL, databaseName.c_str(), username.c_str(), password.c_str());
      
   // Check if an error occured while connecting to the database.
   if (PQstatus(_handles[index]->__conn) == CONNECTION_BAD)
   {
      string err = "_pgsqlConnect(): ";
      err += PQerrorMessage(_handles[index]->__conn);

      PQfinish(_handles[index]->__conn);
      _handles[index]->__conn = NULL;
     
      throw ErrorConnecting(err);
   }

   // Set the date time format to ISO standard format using a query.
   PGresult *r = PQexec(_handles[index]->__conn, "SET DATESTYLE TO 'ISO'");
   if (!r || PQresultStatus(r) != PGRES_COMMAND_OK)
   {
      string err = "_pgsqlConnect(): Unable to set the iso date format on the connection using \"SET DATESTYLE TO 'ISO'\". ";
      if (r)
      {
         err += PQresultErrorMessage(r);
         PQclear(r);
      }
      
      PQfinish(_handles[index]->__conn);
      _handles[index]->__conn = NULL;
     
      throw ErrorConnecting(err);
   }
   
   PQclear(r);

   
   _handles[index]->status = BaseHandle::CONNECTED;
   _handles[index]->lastUsed = time(NULL);
} // PostgresqlConnection::_pgsqlConnect


// -----------------------------------------------------------------------------
// PostgresqlConnection::_pgsqlDisconnect
// -----------------------------------------------------------------------------
void 
PostgresqlConnection::_pgsqlDisconnect(
         int index)
{
   // Make sure the handle is valid and we are not already connected.
   if (index > _numHandles || index < 0)
      throw Error("_pgsqlDisconnect(): Invalid index to database handle.");   

   if (_handles[index]->status == BaseHandle::DISCONNECTED || 
       _handles[index]->status == BaseHandle::UNKNOWN)
      throw NotConnected("_pgsqlDisconnect(): "
         "The database handle does not have a valid connection to the database.");

   // Disconnect from the server.
   PQfinish(_handles[index]->__conn);
   _handles[index]->__conn = NULL;
   _handles[index]->status = BaseHandle::DISCONNECTED;
   _handles[index]->lastUsed = time(NULL);
} // PostgresqlConnection::_pgsqlDisconnect


// -----------------------------------------------------------------------------
// PostgresqlConnection::_pgsqlPing
// -----------------------------------------------------------------------------
void 
PostgresqlConnection::_pgsqlPing(
      int index)
{
   // Make sure the handle is valid.
   if (index > _numHandles || index < 0)
      throw Error("_pgsqlPing(): Invalid index to database handle.");

   // Only ping the connection if the ping interval has expired since the
   //   connection was last used.
	time_t currentTime = time(NULL);
	if (currentTime - _handles[index]->lastUsed >= pingInterval)
	{      
      // Do a dummy query to simulate a ping      
      PGresult *r = PQexec(_handles[index]->__conn, "SELECT CURRENT_TIMESTAMP");
      if (!r || PQresultStatus(r) != PGRES_COMMAND_OK)
      {
         // An error occured while pinging, so reconnect.         
         if (r)
            PQclear(r);
         
         // Try and reconnect the lost connection using PQreset function.
         PQreset(_handles[index]->__conn);
         if (PQstatus(_handles[index]->__conn) == CONNECTION_BAD)
         {            
            string err = "_pgsqlPing(): Unable to ping connection, ";
            err += PQerrorMessage(_handles[index]->__conn);
      
            PQfinish(_handles[index]->__conn);
            _handles[index]->__conn = NULL;
           
            throw ErrorPingingConnection(err);
         }
      }
      else
      {
         // Ping was OK
         PQclear(r);
      }
   }   
} // PostgresqlConnection::_pgsqlPing


// -----------------------------------------------------------------------------
// PostgresqlConnection::_pgsqlQuery
// -----------------------------------------------------------------------------
PGresult*
PostgresqlConnection::_pgsqlQuery(
      int           index,
      const string& sqlStatement)
{
   // Make sure the handle is in the connected used state.
   if (_handles[index]->status != BaseHandle::CONNECTED_USED)
      throw ErrorQuerying("_pgsqlQuery(): The database connection is not valid. May have been terminated by the connection object?");

   //Execute the query
   PGresult *r = PQexec(_handles[index]->__conn, sqlStatement.c_str());      
   if (!r || PQresultStatus(r) == PGRES_BAD_RESPONSE || PQresultStatus(r) == PGRES_FATAL_ERROR)
   {         
      string err = "_pgsqlQuery(): ";
      err += "SQL statement: ";
      err += sqlStatement;
      
      if (r)
      {
         err += ", ";
         err += PQresultErrorMessage(r);
         PQclear(r);
      }
      throw ErrorQuerying(err);
   }
   
   // Set the last used
   _handles[index]->lastUsed = time(NULL);
   
   return r;
} // PostgresqlConnection::_pgsqlQuery


//------------------------------------------------------------------------------
// PostgresqlConnection::_freeCollection
//------------------------------------------------------------------------------
void PostgresqlConnection::_freeCollection(
      CollectionType type)
{
   int i;
   switch (type)
   {
      case CONNECTION_HANDLES:
         if (_handles)
         {
            for (i=0; i<_numHandles; i++)
            {
               if (_handles[i])
               {
                  delete _handles[i];
                  _handles[i] = NULL;         
               }
            }
            free(_handles);
            _handles = NULL;
            _numHandles = 0;
         }
      
         break;      
    }
} // PostgresqlConnection::_freeCollection


// -----------------------------------------------------------------------------
// PUBLIC:
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// PostgresqlConnection::PostgresqlConnection
// -----------------------------------------------------------------------------
PostgresqlConnection::PostgresqlConnection(
      int argc, 
      const char** argv)
:  
   BaseConnection(PGSQL_DRIVERNAME),
   _numHandles(0),
   _handles(NULL)
{
   // Store any arguments in a easy to use structure. (Must be even number)
   // All argument names will be in lowercase.
   if (argc % 2 == 0)
      for (int i=0; i<argc; i+=2)
      {
         // Check for SSL been required
         if (strcasecmp(argv[i], "requireSSL") == 0)
            if (strcasecmp(argv[i+1], "yes") == 0)
               _pgsqlOptions.requireSSL = true;
            else
               _pgsqlOptions.requireSSL = false;
         
         // Check for connection timeout
         if (strcasecmp(argv[i], "connectionTimeout") == 0)
            _pgsqlOptions.connectionTimeout = argv[i+1];
         
         // Check for port
         if (strcasecmp(argv[i], "port") == 0)
            _pgsqlOptions.port = argv[i+1];
      }   
} // PostgresqlConnection::PostgresqlConnection


// -----------------------------------------------------------------------------
// PostgresqlConnection::~PostgresqlConnection
// -----------------------------------------------------------------------------
PostgresqlConnection::~PostgresqlConnection()
{
	// Make sure we disconnect from the database.
	if (isConnected)
	{
		disconnect(120);  // 2 Minute timeout.
	}

   // Free any handles.   
   _freeCollection(CONNECTION_HANDLES);
} // PostgresqlConnection::~PostgresqlConnection


// -----------------------------------------------------------------------------
// PostgresqlConnection::connect
// -----------------------------------------------------------------------------
void 
PostgresqlConnection::connect(
      const string &username, 
      const string &password, 
      const string &databaseName, 
      const string &host, 
      int          maxConnections,
      int          minConnections,
      const string &optParam1,
      const string &optParam2)
{
   // Synchronize the function.
   SimpleThread_Synchronize sync(classMutex);

   // Make sure we are not already connected.
   if (isConnected)
      throw ErrorConnecting("connect(): Already connected to the database.");

   // Lower the database name: Postgresql always forces the database name to lowercase when it is created
   string lDatabaseName = databaseName;
   for (int i=0; i<lDatabaseName.length(); i++)
      lDatabaseName[i] = tolower(lDatabaseName[i]);
   
   // Check and set the parameters. (Use the base class for this)
   BaseConnection::connect(username, password, lDatabaseName, host,
      maxConnections, minConnections, optParam1, optParam2);

   // Free any handles used before.
   _freeCollection(CONNECTION_HANDLES);
   
   // Create the collection to hold the needed handle information.
   _numHandles = maxConnections;
   _handles = (PgsqlHandle**)malloc(_numHandles * sizeof(PgsqlHandle*));
   for (int i=0; i<_numHandles; i++)
   {
      _handles[i] = new PgsqlHandle();
      _handles[i]->status = BaseHandle::DISCONNECTED;
   }
      
   // Connect the required specified minimum connections. The rest will be on demand.
   for (int j=0; j<minConnections; j++)
      _pgsqlConnect(j);

   isConnected = true;   
} // PostgresqlConnection::connect


// -----------------------------------------------------------------------------
// PostgresqlConnection::disconnect
// -----------------------------------------------------------------------------
void
PostgresqlConnection::disconnect(
      time_t timeout)
{
   // Synchronize the function.
   SimpleThread_Synchronize sync(classMutex);

   bool doneTimeout = false;

   // Only disconnect if we are connected.
   if (!isConnected)
      throw NotConnected("disconnect(): Not connected to the database.");
     
   // Disconnect all the open connections.     
   for (int i=0; i<_numHandles; i++)
   {
      // Free the query object if it is still attached.
      // TODO

      if (_handles[i]->status == BaseHandle::CONNECTED || 
          _handles[i]->status == BaseHandle::CONNECTED_USED)
      {
         // If the handle is USED wait for the timeout.
         // Only wait once for the timeout for all connections.
         if (_handles[i]->status == BaseHandle::CONNECTED_USED && !doneTimeout)
         {
            SimpleThread::sleep(timeout * 1000);
            doneTimeout = true;
         }
            
         _pgsqlDisconnect(i);
      }
   }
           
   isConnected = false;
} // PostgresqlConnection::disconnect


// -----------------------------------------------------------------------------
// PostgresqlConnection::requestQueryConnection
// -----------------------------------------------------------------------------
void*
PostgresqlConnection::requestQueryConnection()
{
   // This function must act as a fifo stack. The first thread in must get the first 
   //   available connection.
   // Synchronize the function.
   SimpleThread_Synchronize sync(classMutex);
   
   // Make sure we are connected.
   if (!isConnected)
      throw NotConnected("requestQueryConnection(): Not connected to the database.");
 
   // Flag that a request is occuring.
   isRequestQueryConnectionOccuring = true;

   
   // Loop until we have a valid connection or an error.
   // Built in timeout of 1 min per thread to retrieve a connection.
   int i;
   time_t now = time(NULL);
   while (time(NULL) <= now + 60)
   {
      // Try and obtain a connection
      for (i=0; i<_numHandles; i++)
      {
         // We have an available handle thats already connected.
         if (_handles[i]->status == BaseHandle::CONNECTED)
         {
            _handles[i]->queryObject = new PostgresqlQuery(this, i);
            _handles[i]->status = BaseHandle::CONNECTED_USED;

            isRequestQueryConnectionOccuring = false;
            return _handles[i]->queryObject;
         }
         
         // We have an available handle that needs to be connected.
         if (_handles[i]->status == BaseHandle::DISCONNECTED)
         {
            _pgsqlConnect(i);
            _handles[i]->queryObject = new PostgresqlQuery(this, i);
            _handles[i]->status = BaseHandle::CONNECTED_USED;

            isRequestQueryConnectionOccuring = false;
            return _handles[i]->queryObject;
         }
      }

      // Sleep for a second to conserve resources
      SimpleThread::sleep(1000);
   }


   // If we got this far a timeout occured.
   isRequestQueryConnectionOccuring = false;
   throw QueryConnectionTimeout("requestQueryConnection(): A timout occured "
         "while trying to obtain a query connection.");
} // PostgresqlConnection::requestQueryConnection



// -----------------------------------------------------------------------------
// PostgresqlConnection::releaseQueryConnection
// -----------------------------------------------------------------------------
void
PostgresqlConnection::releaseQueryConnection(
   void* queryObject)
{
   // Don't synchronize as we may need to release a connection to allow
   //  requestQueryConnection to get an available one
   
   // Find the handle that has the query connection instance
   int i;
   for (i=0; i<_numHandles; i++)
   {
      if (_handles[i]->queryObject == queryObject)
      {
         _handles[i]->queryObject = NULL;
         _handles[i]->status = BaseHandle::CONNECTED;
         
         // Check if we need to release the connection.
         // We release the connection if the connection is outside of the minimum
         // connections and there is no current request for a connection waiting.
         if (i >= minConnections && !isRequestQueryConnectionOccuring)
         {
            _pgsqlDisconnect(i);
         }
        
         break;
      }
   }
} // PostgresqlConnection::releaseQueryConnection
